﻿#light

module TypeRepository

open System
open System.Collections.Generic
open Might.Infrastructure.ModelicaDOM
open System.Reflection

(*
exception AttributeException of string    

type MapperAttribute = class
    inherit Attribute
    val mutable _source:string
    member this.Source
        with get() = this._source
        and set(value) = this._source <- value
    new(source) = {
            _source = source
        }
end

type DefaultMapperAttribute = class
    inherit Attribute
    new() = {}
end

type ArrayIndexMappingAttribute = class
    inherit Attribute
    val mutable _order: int
    member this.Order
        with get() = this._order
        and set(value) = this._order <- value
    new(order) = {
        _order = order
    }
end

type ConstructorParameterOrderMappingAttribute = class
    inherit Attribute
    val mutable _order: int
    member this.Order
        with get() = this._order
        and set(value) = this._order <- value
    new(order) = {
        _order = order
    }
end
*)
type TypeProvider() = class
    // type name * type
    static let _registeredTypesBySource = new Dictionary<string,System.Type>()
    // (type name + name of the property) * target property name
    
    static let _registeredTypesByTypeName = new Dictionary<string,System.Type>()
    
    static let _mappedPropertiesCache = new Dictionary<string,string>()
    // type name * target property name
    static let _defaultPropertiesCache = new Dictionary<string,string>()
    
    // type name * int position   
    static let _mappedArrayPropertiesCache  = new Dictionary<string,string>()   
    
    // type name * int position   
    static let _mappedConstructorPropertiesCache  = new Dictionary<string,string>()   
                       
    static member RegisterType(regType:System.Type) = 
        let att = regType.GetCustomAttributes(typeof<MappingAttribute>,false)
        match att.Length with
        | 0 -> raise( AttributeException("Class does not contain MapperAttribute"))
        | 1 -> 
            //System.Console.WriteLine("Registering type: " + regType.Name)
            let mapAtt = att.[0] :?> MappingAttribute
            _registeredTypesBySource.Add(mapAtt.Source,regType)
            _registeredTypesByTypeName.Add(regType.Name,regType)
        | _ -> raise( AttributeException("Too many MapperAttribute decorations."))
                 
    static member GetRegisteredTypeBySourceName(mapSourceName:string) = 
        match _registeredTypesBySource.TryGetValue(mapSourceName) with
        | true, regType ->
            Some(regType)
        | false,_ -> None
        
    static member GetRegisteredTypeByTypeName(typeName:string) = 
        match _registeredTypesByTypeName.TryGetValue(typeName) with
        | true, regType ->
            Some(regType)
        | false,_ -> None
        
    static member MapProperty(typeName:string,propertyName:string) = 
        match _mappedPropertiesCache.TryGetValue(typeName + "*" + propertyName) with
        | true, mappedName -> 
            Some(mappedName)
        | false, _ -> 
            match TypeProvider.GetRegisteredTypeByTypeName(typeName) with
            | None -> 
                None
            | Some regType -> 
                let mutable foundProperty = None
                for prop in regType.GetProperties() do
                    let att = prop.GetCustomAttributes(typeof<MappingAttribute>,false)
                    match att.Length with                    
                    | 0 -> ()
                    | 1 -> 
                        match (att.[0] :?> MappingAttribute).Source.Equals(propertyName) with
                        | true -> foundProperty <- Some(prop)
                        | false -> ()
                    | _ -> raise( AttributeException("Too many MapperAttribute decorations."))
                match foundProperty with
                | None -> None
                | Some prop -> 
                    _mappedPropertiesCache.Add(typeName + "*" + propertyName,prop.Name)
                    Some(prop.Name)
                    
    static member MapDefaultProperty(typeName:string) = 
        match _defaultPropertiesCache.TryGetValue(typeName) with
        | true, mappedName -> 
            Some(mappedName)
        | false, _ -> 
            match TypeProvider.GetRegisteredTypeByTypeName(typeName) with
            | None -> 
                None
            | Some regType -> 
                
                let propList = List.of_array( regType.GetProperties())
                
                let rec findDefaultProperty(propertyList: Reflection.PropertyInfo list) = 
                    match propertyList with
                    | h :: t ->
                        let att = h.GetCustomAttributes(typeof<DefaultMappingAttribute>,false)
                        match att.Length with                    
                        | 0 -> findDefaultProperty(t)
                        | 1 -> 
                            Some(h)                                                
                        | _ -> raise( AttributeException("Too many MapperAttribute decorations."))
                    | [] -> None
                
                let mutable foundProperty = findDefaultProperty(propList)
                
                match foundProperty with
                | None -> None
                | Some prop -> 
                    _defaultPropertiesCache.Add(typeName,prop.Name)
                    Some(prop.Name)
                    
    static member MapUnknownProperty(typeName:string) = 
        match TypeProvider.GetRegisteredTypeByTypeName(typeName) with
        | None -> 
            None
        | Some regType -> 
            
            let propList = List.of_array( regType.GetProperties())
            
            let rec findUnknownProperty(propertyList: Reflection.PropertyInfo list) = 
                match propertyList with
                | h :: t ->
                    let att = h.GetCustomAttributes(typeof<UnknownMappingAttribute>,false)
                    match att.Length with                    
                    | 0 -> findUnknownProperty(t)
                    | 1 -> 
                        Some(h)                                                
                    | _ -> raise( AttributeException("Too many MapperAttribute decorations."))
                | [] -> None
            
            findUnknownProperty(propList)            
                    
    static member MapArrayProperty(typeName:string,index:int) = 
        match _mappedArrayPropertiesCache.TryGetValue(typeName + "*" + index.ToString()) with
        | true, mappedName -> 
            Some(mappedName)
        | false, _ -> 
            match TypeProvider.GetRegisteredTypeByTypeName(typeName) with
            | None -> 
                None
            | Some regType -> 
                let mutable foundProperty = None
                for prop in regType.GetProperties() do
                    let att = prop.GetCustomAttributes(typeof<ArrayIndexMappingAttribute>,false)
                    match att.Length with                    
                    | 0 -> ()
                    | 1 -> 
                        match (att.[0] :?> ArrayIndexMappingAttribute).Position.Equals(index) with
                        | true -> foundProperty <- Some(prop)
                        | false -> ()
                    | _ -> raise( AttributeException("Too many MapperAttribute decorations."))
                match foundProperty with
                | None -> None
                | Some prop -> 
                    _mappedArrayPropertiesCache.Add(typeName + "*" + index.ToString(),prop.Name)
                    Some(prop.Name)
        
    static member MapConstructorProperty(typeName:string,index:int) = 
        match _mappedConstructorPropertiesCache.TryGetValue(typeName + "*" + index.ToString()) with
        | true, mappedName -> 
            Some(mappedName)
        | false, _ -> 
            match TypeProvider.GetRegisteredTypeByTypeName(typeName) with
            | None -> 
                None
            | Some regType -> 
                let mutable foundProperty = None
                for prop in regType.GetProperties() do
                    let att = prop.GetCustomAttributes(typeof<ConstructorIndexMappingAttribute>,false)
                    match att.Length with                    
                    | 0 -> ()
                    | 1 -> 
                        match (att.[0] :?> ArrayIndexMappingAttribute).Position.Equals(index) with
                        | true -> foundProperty <- Some(prop)
                        | false -> ()
                    | _ -> raise( AttributeException("Too many MapperAttribute decorations."))
                match foundProperty with
                | None -> None
                | Some prop -> 
                    _mappedConstructorPropertiesCache.Add(typeName + "*" + index.ToString(),prop.Name)
                    Some(prop.Name)  
    static member MapEnumCase(enumName:string,caseName:string) = 
        let enumTypeOpt = TypeProvider.GetRegisteredTypeBySourceName(enumName);
        match enumTypeOpt with
        | Some enumType ->            
                        
            let mutable index = 0
            let fields = enumType.GetFields()
            let mutable foundField: string option = None
            while ((foundField.IsNone = true) && (index < fields.Length)) do
                let attr = fields.[index].GetCustomAttributes(typeof<MappingAttribute>,false)
                if attr.Length > 0 then                    
                    if((attr.[0] :?> MappingAttribute).Source.Equals(caseName)) then
                        foundField <- Some(fields.[index].Name)                    
                index <- index + 1   
            done 
            match foundField with
            | Some fieldName ->
                (enumType,fieldName)
            | None ->
               raise(ArgumentException ("Found Enum " + enumName + ", but case " + caseName  + " not found")) 
        | None ->
            raise(ArgumentException ("Enum " + enumName + " not found"))
end
